﻿namespace PauloMorgado.SharePoint.ListExporter
{
    using Mono.Options;
    using PauloMorgado.SharePoint.Services.Lists;
    using PauloMorgado.SharePoint.Services.Views;
    using System;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.ServiceModel;
    using System.Xml;
    using System.Xml.Linq;
    using String = PauloMorgado.SharePoint.String;

    class Options
    {
        public Uri SiteUri = null;
        public string ListName = null;
        public string ViewName = null;
        public string[] FieldNames = null;
        public XElement Query = null;
        public XElement QueryOptions = null;
        public string OutputPath = null;
        public string UserName = null;
        public string Password = null;
        public string RootElementName = "root";
        public string RowElementName = "row";
        public bool ShowInfo = false;
        public bool ShowHelp = false;
        public bool ShowVerbose = false;
        public bool StripFieldPrefix = true;
        public bool FormatXmlOutput = true;
        public HttpClientCredentialType HttpClientCredentialType = HttpClientCredentialType.Ntlm;
    }

    class Program
    {
        private static bool hasInfoBeenShown;

        static void Main(string[] args)
        {
#if DEBUG
            foreach (var arg in args)
            {
                Console.WriteLine(arg);
            }
#endif
            try
            {
                Options options = ParseOptions(args);

                if (options.ShowHelp)
                {
                    ShowHelp(Console.Out);
                    return;
                }

                ExportList(options);
            }
            catch (Exception ex)
            {
                ShowError(ex);
            }
        }

        private static void ShowHelp(TextWriter writer)
        {
            ShowInfo(writer);

            writer.WriteLine(
@"                                  site|url=<URL> :: URL of the SharePoint site
                               l|ListName=<name> :: Name of the SharePoint list
                               v|ViewName=<name> :: Name of the view of the SharePoint list
                      f|FieldNames=<field names> :: Comma separated list of field names
                        q|Query=<XML query body> :: The body of the XML query
        qo|QueryOptions=<XML query options body> :: The body of the XML query options
                          u|UserName=<user name> :: The user account name
                           p|Password=<password> :: The user account password
c|CredentialType=[Basic|Certificate
    |Digest|InheritedFromHost|None|Ntlm|Windows] :: Credential type
                         o|out|OutputPath=<path> :: The path of the output file
                        r|RootElementName=<name> :: Name of the XML document root element
                         r|RowElementName=<name> :: Name of the XML document row element
                               v|ViewName=<name> :: Name of the view of the SharePoint list
                                         s|strip :: Strip 'ows_' field prefix
                                        t|format :: Format XML output
                                          !|info :: Informational output
                                       #|verbose :: Verbose output
                                        h|?|help :: Show this help
------------------------------------------------------------------------------------------------------------

");
        }

        private static void ShowInfo(TextWriter writer)
        {
            if (!hasInfoBeenShown)
            {
                writer.WriteLine(
                    "Paulo Morgado's SharePoint List Exporter version {0}\nPaulo Morgado © 2012\n\n",
                    Assembly.GetExecutingAssembly().GetName().Version);

                hasInfoBeenShown = true;
            }
        }

        private static void ShowError(Exception ex)
        {
            TextWriter writer = Console.Error;

            var oex = ex as OptionException;
            if (oex != null)
            {
                ShowHelp(writer);

                writer.WriteLine(
                    "\n{0}: {1}\n",
                    oex.OptionName,
                    oex.Message);
#if DEBUG
                ex = oex.InnerException;
                if (ex != null)
                {
                    writer.WriteLine(
                        "\n{0}: {1}\n",
                        ex.GetType().Name,
                        ex);
                }
#endif
            }
            else
            {
                ShowInfo(writer);

                writer.WriteLine(
                    "\n{0}: {1}\n",
                    ex.GetType().Name,
#if DEBUG
 ex
#else
                ex.Message
#endif
);
            }
        }

        private static Options ParseOptions(string[] args)
        {
            var options = new Options();

            var opstionSet = new OptionSet()
                .Add("site|url=", "SharePoint site address", (v) => options.SiteUri = new Uri(v.EndsWith("/") ? v : v + "/"))
                .Add("l|ListName=", "SharePoint list name", (v) => options.ListName = v)
                .Add("v|ViewName=", "SharePoint view name", (v) => options.ViewName = v)
                .Add("f|FieldNames=", "Names of the fields to export", (v) => { if (!String.IsNullOrWhiteSpace(v)) { options.FieldNames = v.Split(new char[] { ',', ';' }); } })
                .Add("q|Query=", "XML Query Body", (v) => { if (!String.IsNullOrWhiteSpace(v)) { options.Query = XElement.Parse("<Query>" + v + "</Query>"); } })
                .Add("qo|QueryOptions=", "XML Query Options Body", (v) => { if (!String.IsNullOrWhiteSpace(v)) { options.QueryOptions = XElement.Parse("<QueryOptions>" + v + "</QueryOptions>"); } })
                .Add("u|UserName=", "SharePoint user name", (v) => options.UserName = v)
                .Add("p|Password=", "SharePoint user password", (v) => options.Password = v)
                .Add("c|CredentialType=", "SharePoint credential type", (v) => { options.HttpClientCredentialType = (HttpClientCredentialType)(Enum.Parse(typeof(HttpClientCredentialType), v, true)); })
                .Add("o|out|OutputPath=", "Output file path", (v) => options.OutputPath = v)
                .Add("r|RootElementName=", "XML Document root element name", (v) => options.RootElementName = v)
                .Add("e|RowElementName=", "XML Document row element name", (v) => options.RowElementName = v)
                .Add("s|strip", "Strip ows_ field prefix", (_) => { options.StripFieldPrefix = true; })
                .Add("t|formatted", "Format XML output", (_) => { options.FormatXmlOutput = true; })
                .Add("!|info", "Show informational output", (_) => { options.ShowInfo = true; })
                .Add("#|verbose", "Show verbose output", (_) => { options.ShowVerbose = true; })
                .Add("?|help", "Show help", (_) => { options.ShowHelp = true; });

            opstionSet.Parse(args);

            if (options.SiteUri == null)
            {
                throw new OptionException("Missing SharePoint site address", "-site");
            }

            if (String.IsNullOrWhiteSpace(options.ListName))
            {
                throw new OptionException("Missing SharePoint list name", "-ListName");
            }

            if (String.IsNullOrWhiteSpace(options.OutputPath))
            {
                options.OutputPath = String.IsNullOrWhiteSpace(options.ViewName)
                    ? options.ListName
                    : (options.ListName + '_' + options.ViewName + ".xml");
            }

            options.OutputPath = Path.GetFullPath(options.OutputPath);

            return options;
        }

        private static void ExportList(Options options)
        {
            if (options.ShowVerbose || options.ShowInfo)
            {
                ShowInfo(Console.Out);
                Console.WriteLine(
@"     SharePoint Site URL: {0}
    SharePoint List Name: {1}
    SharePoint View Name: {2}
    SharePoint User Name: {3}
  SharePoint Field Names: {4}
        SharePoint Query: {5}
SharePoint Query Options: {6}
        Output File Path: {7}
Output Root Element Name: {8}
 Output Row Element Name: {9}
        Formatted Output: {10}
 Strip Field Name Prefix: {11}
",
                    options.SiteUri,
                    options.ListName,
                    options.ViewName,
                    options.UserName,
                    options.FieldNames == null ? null : string.Join(",", options.FieldNames),
                    options.Query == null ? null : options.Query.ToString(SaveOptions.DisableFormatting),
                    options.QueryOptions == null ? null : options.QueryOptions.ToString(SaveOptions.DisableFormatting),
                    options.OutputPath,
                    options.RootElementName,
                    options.RowElementName,
                    options.FormatXmlOutput,
                    options.StripFieldPrefix);
            }

            var credential = String.IsNullOrWhiteSpace(options.UserName) ? new NetworkCredential() : new NetworkCredential(options.UserName, options.Password);

            string viewId;
            if (!String.IsNullOrWhiteSpace(options.ViewName))
            {
                var viewsService = ViewsService.CreateViewsChannel(options.SiteUri, options.HttpClientCredentialType, credential);

                var r = viewsService.GetViewCollection(new GetViewCollectionRequest { Body = new GetViewCollectionRequestBody { listName = options.ListName } });

                viewId = r.Body.GetViewCollectionResult.Elements().Single(e => (string)e.Attribute("DisplayName") == options.ViewName).Attribute("Name").Value;
            }
            else
            {
                viewId = null;
            }

            var listsService = ListsService.CreateListsChannel(options.SiteUri, options.HttpClientCredentialType, credential);

            var getListItemsRequest = new GetListItemsRequest
                {
                    Body = new GetListItemsRequestBody
                    {
                        listName = options.ListName,
                        viewName = viewId
                    }
                };

            if (options.FieldNames != null)
            {
                getListItemsRequest.Body.viewFields = new XElement("ViewFields",
                    from field in options.FieldNames
                    select new XElement("FieldRef", new XAttribute("Name", field)));
            }

            if (options.Query != null)
            {
                getListItemsRequest.Body.query = options.Query;
            }

            if (options.QueryOptions != null)
            {
                getListItemsRequest.Body.queryOptions = options.QueryOptions;
            }

            using (var writer = System.Xml.XmlWriter.Create(options.OutputPath, new XmlWriterSettings { Indent = options.FormatXmlOutput }))
            {
                try
                {
                    writer.WriteStartDocument(false);

                    try
                    {
                        var rowElementName = XmlConvert.EncodeLocalName(options.RowElementName);

                        writer.WriteStartElement(XmlConvert.EncodeLocalName(options.RootElementName));

                        foreach (var item in listsService.GetAllListItems(getListItemsRequest))
                        {
                            try
                            {
                                writer.WriteStartElement(rowElementName);

                                if (options.ShowVerbose)
                                {
                                    Console.WriteLine();
                                }

                                var fieldsAttributes = (options.FieldNames == null) || (options.FieldNames.Length == 0)
                                    ? item.Attributes()
                                    : from f in options.FieldNames
                                      from a in item.Attributes()
                                      where a.Name.LocalName.Equals(XmlConvert.EncodeLocalName("ows_" + f))
                                      select a;

                                foreach (var attribute in fieldsAttributes)
                                {
                                    var fieldName = attribute.Name.LocalName;

                                    if (options.StripFieldPrefix)
                                    {
                                        fieldName = fieldName.Substring(4);
                                    }

                                    var value = attribute.Value;

                                    writer.WriteElementString(fieldName, value);

                                    if (options.ShowVerbose)
                                    {
                                        Console.WriteLine("{0}: {1}", fieldName, value);
                                    }
                                }
                            }
                            finally
                            {
                                writer.WriteEndElement();
                            }
                        }
                    }
                    finally
                    {
                        writer.WriteEndElement();
                    }
                }
                finally
                {
                    writer.WriteEndDocument();
                }
            }
        }
    }
}
